﻿using System;

using System.Collections.Generic;
using System.Text;
using Silvermoon.Controls.Interfaces;
using System.Windows.Forms;
using System.Drawing;
using Silvermoon.OpenGL;
using Silvermoon.Controls.Sys;
using Silvermoon.Shapes;
using Silvermoon.Utils;
using Silvermoon.Windows;
using Silvermoon.Controls.Base;
using System.Diagnostics;
using Silvermoon.UI;
using Silvermoon.Images;
using Silvermoon.Controls.Textboxes;
using Silvermoon.Animations;
using Silvermoon.Controls.Forms;


namespace Silvermoon.Controls
{

    /// <summary>
    /// base class for all Textboxes.
    /// </summary>
    public abstract class TextboxBase : ChromeControl, IInputControl
    {
        #region const

        const int PRESSED_BIT = 1;
        const int ALLOW_PRESS = 2;

        public const string ShowGrippersState = "ShowGrippers";
        public const string HideGrippersState = "HideGrippers";

        #endregion
        #region fields

        /// <summary>
        /// Reserved for boolean values.
        /// </summary>
        protected ushort bitmask;

        static readonly char[] wordbreaks = new char[] { ' ', '\t', '\n', '\r' };

        private static UITimer caretTimer;
        private static GrabberShape startGrabber, endGrabber;

        public static readonly Color CaretColor = PhoneColors.DeepBlue;

        private DragTarget dragTarget;
        private bool isLongPressed;
        private FontSprite font;
        private static TextboxBase focusedTextbox;
        private int selLen, selStart;
        private string text;
        private bool isCaretVisible = false;
        private RectangleShape caretShape;

        /// <summary>
        /// Gets the toolbar for selection.
        /// </summary>
        internal static readonly ToolbarBase SelectToolbar = new TextboxToolbar();

        /// <summary>
        /// Gets the toolbar for cut copy paste.
        /// </summary>
        internal static readonly ToolbarBase CutCopyPasteToolbar = new TextboxClipToolbar();

        #endregion
        #region accessors
        #endregion
        #region ctors/dtors

        #region manager

        #endregion
        static TextboxBase()
        {
            VisualStateManager.RegisterClassManager(typeof(TextboxBase), TextboxStateManagers.BaseManager);

            caretTimer = new UITimer
            {
                Interval = 500
            };
            caretTimer.Tick += CaretTick;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TextboxBase"/> class.
        /// </summary>
        public TextboxBase()
            : base()
        {

            caretShape = new RectangleShape
            {
                Color = CaretColor
            };
            ControlFlags |= ControlFlags.CanPan | ControlFlags.EnableLongPress;
        }

        protected override void Dispose(bool dispose)
        {
            HideGrippers();

            if (caretShape != null) caretShape.Dispose();
            caretShape = null;
            base.Dispose(dispose);
        }

        #endregion
        #region props

        /// <summary>
        /// Gets or sets the allowed characters for input. If set to null, all characters are allowed.
        /// </summary>
        public string AllowedCharacters { get; set; }

        /// <summary>
        /// Gets or sets the color for highlighted text.
        /// </summary>
        public virtual Color HighlightColor
        {
            get { return PhoneColors.Blue; }
            set { }
        }

        protected override void ApplyStyle()
        {
            base.ApplyStyle();
            if (Color.IsEmpty) Color = PhoneColors.Light;
            SetState(VState.Unfocused);
        }


        /// <summary>
        /// Gets the focused textbox otherwise null.
        /// </summary>
        public static TextboxBase FocusedTextbox
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the rectangle for the selection start.
        /// </summary>
        protected Rectangle SelectionStartRect
        {
            get { return GetSelectionStartRect(); }
        }

        /// <summary>
        /// Gets the rectangle for the selection end;
        /// </summary>
        protected Rectangle SelectionEndRect
        {
            get { return GetSelectionEndRect(); }
        }

        /// <summary>
        /// Gets the rectangle for the caret.
        /// </summary>
        protected Rectangle CaretRect
        {
            get { return GetCaretRect(); }
        }

        /// <summary>
        /// Gets the shape for the caret.
        /// </summary>
        protected RectangleShape CaretShape
        {
            get
            {
                return caretShape;
            }
        }


        /// <summary>
        /// Gets the tooltip for text selection.
        /// </summary>
        public static TextSelectToolTip SelectToolTip
        {
            get { return TextSelectToolTip.Instance; }
        }

        /// <summary>
        /// Gets the tooltip for cut copy paste.
        /// </summary>
        public static CutCopyPasteToolTip CutCopyPasteToolTip
        {
            get { return CutCopyPasteToolTip.Instance; }
        }

        /// <summary>
        /// Gets the shape for the gripper to change the beginning of the selected text.
        /// </summary>
        protected Shape EndGrabber
        {
            get
            {
                if (endGrabber == null) endGrabber = new GrabberShape();
                endGrabber.Color = HighlightColor;
                return endGrabber;
            }
        }

        /// <summary>
        /// Gets the shape for the gripper to change the end of the selected text.
        /// </summary>
        protected Shape StartGrabber
        {
            get
            {
                if (startGrabber == null) startGrabber = new GrabberShape();
                startGrabber.Color = HighlightColor;
                return startGrabber;
            }
        }

        /// <summary>
        /// Gets or sets the font to use.
        /// </summary>
        public FontSprite Font
        {
            get
            {
                return font ?? FontSprite.Regular;
            }
            set
            {
                if (font != value)
                {
                    font = value;
                    OnFontChanged();
                }
            }
        }


        /// <summary>
        /// Gets or sets whether the textbox accepts returns.
        /// </summary>
        public virtual bool AcceptReturn
        {
            get { return false; }
            set { }
        }

        /// <summary>
        /// Gets or sets whether the text is read only.
        /// </summary>
        public virtual bool ReadOnly
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the text.
        /// </summary>
        public string Text
        {
            get
            {
                return text ?? string.Empty;
            }
            set
            {
                value = ValidateText(value);
                if (text != value)
                {
                    SetTextCore(value);
                    OnTextAssigned();
                }
            }
        }

        private void SetTextCore(string value)
        {
            Invalidate(InvalidationFlag.Layout);
            string oldValue = text;
            text = value;
            OnTextChanged(oldValue);
        }

        /// <summary>
        /// Occures only when a new Text property is directly assigned.
        /// </summary>
        protected virtual void OnTextAssigned()
        {
            Select(text.Length, 0);
        }

        /// <summary>
        /// Occurs before text is changed.
        /// </summary>
        /// <param name="oldValue">Old text value.</param>
        /// <param name="newValue">New text value.</param>
        protected virtual void OnTextChanging(string oldValue, string newValue)
        {
        }

        /// <summary>
        /// Occurs after text is changed either by setting Text property, or by inserting or remove one ore more characters.
        /// See also <typeparamref name="OnTextAssigned"/>.
        /// </summary>
        protected virtual void OnTextChanged(string oldValue)
        {
            Invalidate(InvalidationFlag.Text);
            RaiseEvent(TextChangedEvent);
        }

        protected DragTarget DragTarget
        {
            get { return dragTarget; }
            set
            {
                dragTarget = value;
                OnDragTargetChanged();
            }
        }

        /// <summary>
        /// Gets or sets the maximum length for the text. If set to 0, the length is unlimited.
        /// </summary>
        public int MaxLength
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets a value indicating the currently selected text in the control.
        /// </summary>
        public int SelectionStart
        {
            get
            {
                return selStart;
            }
            set
            {
                if (selStart != value) Select(value, 0);
            }
        }

        /// <summary>
        /// Gets the the text index of the character that is the anchor whilst marking (in other words: this is the index where the user starts to select.)
        /// </summary>
        protected int SelectionAnchor
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets or sets the number of characters selected in the text box.
        /// </summary>
        public int SelectionLength
        {
            get
            {
                return selLen;
            }
            set
            {
                if (selLen != value) Select(selStart, value);
            }
        }

        /// <summary>
        /// Gets or sets the number of characters selected in the text box.
        /// </summary>
        public virtual string SelectedText
        {
            get
            {
                return Text.Substring(selStart, selLen);
            }
            set
            {
                InsertText(value);
            }
        }

        /// <summary>
        /// Gets whether the caret is currently shown.
        /// </summary>
        public bool IsCaretVisible
        {
            get
            {
                return isCaretVisible && Enabled && !ReadOnly && selLen == 0 && IsFocused;
            }
            set
            {
                if (isCaretVisible != value)
                {
                    isCaretVisible = value;
                    Invalidate(InvalidationFlag.Appearance);
                }
            }
        }

        protected virtual void OnFontChanged()
        {
            Invalidate(InvalidationFlag.Appearance);
        }

        protected static TextboxBase FocusedTextBox
        {
            get
            {
                return focusedTextbox;
            }
            set
            {
                if (focusedTextbox != value)
                {
                    if (focusedTextbox != null) focusedTextbox.IsCaretVisible = false;
                    focusedTextbox = value;

                    if (value != null) value.IsCaretVisible = true;
                    caretTimer.Enabled = value != null;
                }
            }
        }

        /// <summary>
        /// Gets or sets whether the button is pressed.
        /// </summary>
        public bool IsPressed
        {
            get { return (bitmask & PRESSED_BIT) != 0; }
            set
            {
                if (!AllowPress) value = false;
                if (IsPressed != value)
                {
                    bitmask = (ushort)(value ? bitmask | PRESSED_BIT : bitmask & ~PRESSED_BIT);
                    OnPressedChanged();
                    Invalidate(InvalidationFlag.Appearance);
                }
            }
        }

        /// <summary>
        /// Gets or sets wether  IsPressed state changes when the button is touched and released.
        /// </summary>
        public bool AllowPress
        {
            get { return (bitmask & ALLOW_PRESS) != 0; }
            set { bitmask = (ushort)(value ? bitmask | ALLOW_PRESS : bitmask & ~ALLOW_PRESS); }
        }

        #endregion
        #region methods

        protected virtual void OnPressedChanged()
        {
            SetState(IsPressed ? VState.Pressed : VState.Normal);
        }


        private void SelectWord(TapEventArgs e)
        {
            e.Handled = true;
            HideClipboard();
            SetCaret(e.Position, false);
            SelectWord();
            DragTarget = DragTarget.None;
            ShowClipboard();
        }

        protected override void OnLayout()
        {
            base.OnLayout();
            if (CheckFlag(InvalidationFlag.LayoutAppearance))
            {
                Shape startGrabber = StartGrabber;
                Shape endGrabber = EndGrabber;
                Shapes.Remove(startGrabber);
                Shapes.Remove(endGrabber);
                if (SelectionLength > 0)
                {
                    Rectangle r = SelectionEndRect;
                    Point p1 = SelectionStartRect.Location;
                    p1.Offset(-endGrabber.Width / 2, -endGrabber.Height + 2);
                    Point p2 = new Point(r.Right - endGrabber.Width / 2, r.Bottom - 2);

                    startGrabber.Location = p1;
                    endGrabber.Location = p2;
                    Shapes.AddLast(endGrabber);
                    Shapes.AddLast(startGrabber);
                }

            }
            if (CheckFlag(InvalidationFlag.Layout | InvalidationFlag.Size))
            {
                Shape chrome = Chrome;
                if (chrome != null) chrome.Bounds = BackgroundRect;

            }
        }

        /// <summary>
        /// Gets the rectangle for the caret bounds.
        /// </summary>
        /// <returns>Rectangle.</returns>
        protected abstract Rectangle GetCaretRect();

        /// <summary>
        /// Gets the rectangle for the selection start bounds.
        /// </summary>
        /// <returns>Rectangle.</returns>
        protected abstract Rectangle GetSelectionStartRect();

        /// <summary>
        /// Gets the rectangle for the selection end bounds.
        /// </summary>
        /// <returns>Rectangle.</returns>
        protected abstract Rectangle GetSelectionEndRect();

        /// <summary>
        /// Occurs when <see cref="P:DragTarget"/> property changes.
        /// </summary>
        protected virtual void OnDragTargetChanged()
        {
        }

        /// <summary>
        /// Ensures that the caret is visible in the none overlapped area of the window.
        /// </summary>
        /// <param name="animated"></param>
        protected abstract void EnsureCaretIsVisible(bool animated);

        /// <summary>
        /// When the textbox is pressed for a longer time, switch to cut&copy mode:
        /// </summary>
        protected internal override void OnHold(TouchEventArgs e)
        {
            base.OnHold(e);
            if (!e.Handled)
            {
                switch (dragTarget)
                {
                    case DragTarget.Caret:
                    case DragTarget.None:
                        isLongPressed = true;
                        e.Handled = true;

                        SetCaret(e.Position, true);
                        break;

                }
            }
        }

        private void SetCaret(Point position, bool showClipboard)
        {
            DragTarget = DragTarget.Caret;
            UpdateDragTarget(PointToClient(position));

            int downIndex = GetIndexFromPoint(PointToClient(position));
            Select(downIndex, 0);
            if (showClipboard) ShowClipboard();
        }

        protected internal override void OnDoubleTap(TapEventArgs e)
        {
            if (!e.Handled)
            {
                e.Handled = true;
                SelectWord(e);
            }
            base.OnDoubleTap(e);
        }

        protected internal override void OnTap(TapEventArgs e)
        {
            if (!e.Handled && !ReadOnly)
            {
                switch (dragTarget)
                {
                    case DragTarget.None:
                        //e.Handled = true;
                        //if (focused && SelectionLength == 0) SelectWord(e);
                        //else Select(SelectionStart, 0);
                        break;

                    case DragTarget.Caret:
                        e.Handled = true;
                        Select(SelectionStart, 0);
                        break;
                }
            }
            base.OnTap(e);
        }



        static void CaretTick(object sender, EventArgs e)
        {
            TextboxBase tb = FocusedTextBox;
            if (tb != null)
            {
                if (tb.selLen == 0)
                {
                    tb.IsCaretVisible ^= true;
                }
                else tb.IsCaretVisible = false;
            }
            caretTimer.Restart();
        }

        /// <summary>
        /// Selects parts of the text.
        /// </summary>
        /// <param name="start">Start index of the text to select.</param>
        /// <param name="length">Length of characters to select.</param>
        public void Select(int start, int length)
        {
            //if (IsFocused) TextSelectToolTip.Hide();
            start = ValidateSelectionStart(start);
            //    if (this.selStart == start && this.selLen == length) return;

            string text = Text ?? string.Empty;
            int max = length + start;
            if (max > text.Length) length = max - start;
            this.selStart = start;
            this.selLen = length;
            SelectCore(start, length);
            SelectionAnchor = start; // +length;
            RestartCaretBlink();
            if (SelectionLength > 0)
            {
                ShowGrippers();
                //                ShowClipboard();
            }
            else
            {
                HideGrippers();
            }
            Invalidate(InvalidationFlag.LayoutAppearance);
        }

        private void ShowGrippers()
        {
            SetState(ShowGrippersState);
        }

        private void HideGrippers()
        {
            SetState(HideGrippersState);
        }

        /// <summary>
        /// Performs the needs to display the given selection.
        /// </summary>
        /// <param name="start">Start position in Text.</param>
        /// <param name="length">Length of selection.</param>
        protected abstract void SelectCore(int start, int length);

        /// <summary>
        ///   Selects all text in the text box.
        /// </summary>
        public void SelectAll()
        {
            Select(0, Text.Length);
        }

        /// <summary>
        /// Validates SelectionStart.
        /// </summary>
        /// <param name="value">Value to validate.</param>
        /// <returns>True if valid, otherwise false.</returns>
        protected virtual int ValidateSelectionStart(int value)
        {
            if (value < 0) return 0;
            string text = Text ?? string.Empty;
            if (value >= text.Length) return Math.Max(0, text.Length);
            return value;
        }

        /// <summary>
        /// Validates the text.
        /// </summary>
        /// <param name="text">Text to validate.</param>
        /// <returns>Validated text.</returns>
        protected virtual string ValidateText(string text)
        {
            if (text == null) text = string.Empty;
            if (MaxLength > 0 && text.Length > MaxLength) text = text.Substring(0, MaxLength);

            return text;
        }

        protected virtual void EndKey()
        {
            Select(0, Text.Length);
            EnsureCaretIsVisible(false);
        }

        protected virtual void HomeKey()
        {
            Select(0, 0);
            EnsureCaretIsVisible(true);
        }

        protected virtual void RightKey()
        {
            int w = Math.Max(1, selLen);
            Select(selStart + w, 0);
            EnsureCaretIsVisible(false);
        }

        protected virtual void UpKey()
        {
        }

        protected virtual void DownKey()
        {
        }

        protected virtual void LeftKey()
        {
            Select(selStart - 1, 0);
            EnsureCaretIsVisible(false);
        }

        protected virtual void Delete()
        {
            if (selStart < Text.Length)
            {
                SetTextCore(Text.Remove(selStart, selLen > 0 ? selLen : 1));
                Select(selStart, 0);
            }
            EnsureCaretIsVisible(false);
        }

        protected virtual void EnterKey()
        {
            //            if (!AcceptReturn) Silvermoon.Core.Screen.Current.Focus(null);
            if (AcceptReturn) InsertChar('\n');

        }

        protected virtual void TabKey()
        {
            InsertChar('\t');
        }

        protected virtual void EscapeKey()
        {
        }

        protected virtual void Backspace()
        {
            if (selLen > 0) Delete();
            else
            {
                if (selStart > 0)
                {
                    int start = ValidateSelectionStart(selStart - 1);
                    SetTextCore(Text.Remove(start, selLen + 1));
                    Select(start, 0);
                    EnsureCaretIsVisible(false);
                }
            }
        }

        public void InsertChar(char c)
        {
            if (!Enabled) return;
            if (IsCharacterAllowed(c))
            {
                string text = Text;
                int startSel = this.selStart;
                if (selLen > 0) text = text.Remove(startSel, selLen);
                SetTextCore(text.Insert(startSel, c.ToString()));
                Select(startSel + 1, 0);
                EnsureCaretIsVisible(false);
            }
        }

        private void InsertText(string text)
        {
            if (!Enabled) return;
            string currentText = Text;
            int startSel = this.selStart;
            if (selLen > 0) currentText = currentText.Remove(startSel, selLen);
            SetTextCore(currentText.Insert(startSel, text));
            Select(startSel + text.Length, 0);
            EnsureCaretIsVisible(false);
        }

        public override void OnGotFocus()
        {
            TextboxBase.FocusedTextbox = this;
            base.OnGotFocus();
            if (CanFocus)
            {
                FocusedTextBox = this;
                if (!ReadOnly) Select(SelectionStart, SelectionLength); else Window.IsKeyboardVisible = false;
            }
            else Window.IsKeyboardVisible = false;
            Invalidate(InvalidationFlag.LayoutAppearance);
        }

        /// <summary>
        /// Gets  whether the control can receive focus.
        /// </summary>
        /// <value></value>
        public override bool CanFocus
        {
            get
            {
                return Enabled & Visible; // &!this.ReadOnly;
            }
        }

        /// <summary>
        /// Occurs when the control lost the input focus.
        /// </summary>
        public override void OnLostFocus()
        {
            if (!(Screen.FocusedControl is IInputControl)) HideKeyboard();
            HideGrippers();
            HideClipboard();
            TextboxBase.FocusedTextbox = null;
            base.OnLostFocus();
            if (SelectionLength > 0) Select(SelectionStart, 0);
            FocusedTextBox = null;
            Invalidate(InvalidationFlag.LayoutAppearance);
        }

        private void HideKeyboard()
        {
            Window w = this.Window;
            if (w != null) w.IsKeyboardVisible = false;
        }


        internal protected override void OnTouchDown(TouchEventArgs e)
        {
            bool handled = e.Handled;
            if (!e.Handled)
            {
                if (Enabled) IsPressed = true;
            }
            base.OnTouchDown(e);
            isLongPressed = false;
            HideTooltipClipboard();
            if (!handled)
            {
                if (!ReadOnly) e.Handled = true;
                DragTarget = DetermineDragTarget(PointToClient(e.Position));
                UpdateDragTarget(PointToClient(e.Position));
            }
        }

        protected internal override void OnTouchUp(TouchEventArgs e)
        {
            base.OnTouchUp(e);
            IsPressed = false;
            if (!e.IsTouched)
            {
                //EnsureCaretIsVisible(IsInitialized);
                if (!ReadOnly) EnsureCaretIsVisible(false);
                isLongPressed = false;
            }
        }

        protected internal override void OnEndPan(EventArgs e)
        {
            if (isLongPressed || SelectionLength > 0)
            {
                UpdateToolTip();
                ShowToolTip();
            }
            base.OnEndPan(e);
        }

        /// <summary>
        /// Determines which target to drag depending on the specified client point.
        /// </summary>
        /// <param name="point">Point in client coordinates.</param>
        /// <returns>DragTarget.</returns>
        protected virtual DragTarget DetermineDragTarget(Point point)
        {
            // specifies the radius in which the target is detected:
            const int radius = 48;

            if (SelectionLength == 0)
            {
                int distance = GetDistance(CaretRect.Location, point);
                return distance >= radius ? DragTarget.None : DragTarget.Caret;
            }
            else
            {
                Rectangle rect = SelectionEndRect;
                int dStart = GetDistance(SelectionStartRect.Location, point);
                int dEnd = GetDistance(new Point(rect.Right, rect.Bottom), point);

                if (dStart < radius || dEnd < radius)
                {
                    return dStart <= dEnd ? DragTarget.SelectionStart : DragTarget.SelectionEnd;
                }
                else
                {
                    return DragTarget.None;
                }
            }
        }

        private int GetDistance(Point p1, Point p2)
        {
            int dx = p1.X - p2.X;
            int dy = p1.Y - p2.Y;
            int s = dx * dx + dy * dy;
            return (int)(Math.Sqrt(s));
        }

        private void UpdateDragTarget(Point point)
        {
            int index = GetIndexFromPoint(point);
            if (index < 0) index = 0; else if (index >= Text.Length) index = Text.Length;

            switch (dragTarget)
            {
                case DragTarget.Caret:
                    Select(index, 0);
                    break;

                case DragTarget.SelectionStart:
                    int end = SelectionStart + SelectionLength;
                    if (index >= end) index = end - 1;
                    Select(index, end - index);
                    break;

                case DragTarget.SelectionEnd:
                    if (index <= SelectionStart) index = SelectionStart + 1;
                    Select(SelectionStart, index - SelectionStart);
                    break;
            }
        }

        /// <summary>
        /// Handles panning. For derived textboxes, override <see cref="ScrollText"/> instead if you intend to adjust the content of the text.
        /// </summary>
        protected internal override void OnPan(PanEventArgs e)
        {
            base.OnPan(e);

            e.Handled = true;
            switch (dragTarget)
            {
                case DragTarget.None:
                    ScrollText(e);
                    break;

                default:
                    UpdateDragTarget(PointToClient(e.Position));
                    break;
            }
        }

        /// <summary>
        /// Translates the specified point which holds the coordinates of the touched point relative to the top left corner of the control to.
        /// </summary>
        /// <example>
        /// MultilineTextbox shifts the point to the difference of the point beginning to touch and the point of either the selection start or selection end.
        /// </example>
        protected virtual Point TranslateDragPoint(Point p)
        {
            return p;
        }

        protected internal override void OnBeginPan(PanEventArgs e)
        {
            if (!ReadOnly && Enabled)
            {
                if (dragTarget != DragTarget.None)
                {
                    e.Handled = true;
                }
            }
            HideTooltipClipboard();
        }

        /// <summary>
        /// When overriden, scrolls the content of the textbox to an appropriate location.
        /// This method occurs at OnPan events.
        /// </summary>
        /// <param name="e"></param>
        protected abstract void ScrollText(PanEventArgs e);

        /// <summary>
        /// Gets the index for the character under the specified point.
        /// </summary>
        /// <param name="point">Point coordinates in pixels relative from the top/left corner of the control.</param>
        /// <returns>Index if the character unter that point.</returns>
        protected abstract int GetIndexFromPoint(Point point);

        /// <summary>
        /// Pastes text from clipboard into the textbox.
        /// </summary>
        public void Paste()
        {
            string s = System.Windows.Forms.Clipboard.GetDataObject().GetData(typeof(string)) as string;
            if (s != null) InsertText(s);
        }

        /// <summary>
        /// Cuts the marked text into clipboard.
        /// </summary>
        public void Cut()
        {
            string text = SelectedText;
            InsertText("");
            System.Windows.Forms.Clipboard.SetDataObject(text);
        }

        /// <summary>
        /// Copies the marked text into clipboard.
        /// </summary>
        public void Copy()
        {
            string text = SelectedText;
            System.Windows.Forms.Clipboard.SetDataObject(text);
        }

        public override void OnKeyDown(System.Windows.Forms.KeyEventArgs e)
        {
            HideTooltipClipboard();
            if (!e.Handled)
            {
                e.Handled = true;
                if (ReadOnly) return;
                switch (e.KeyCode)
                {
                    case Keys.Left: LeftKey(); break;
                    case Keys.Right: RightKey(); break;
                    case Keys.Home: HomeKey(); break;
                    case Keys.End: EndKey(); break;
                    case Keys.Delete: Delete(); break;
                    case Keys.Up: UpKey(); break;
                    case Keys.Down: DownKey(); break;
                    default: e.Handled = false; break;
                }
            }
        }

        public override void OnKeyUp(System.Windows.Forms.KeyEventArgs e)
        {
            e.Handled = true;
            if (ReadOnly) return;
        }

        public override void OnKeyPress(System.Windows.Forms.KeyPressEventArgs e)
        {
            HideTooltipClipboard();
            if (!e.Handled && !ReadOnly)
            {
                char c = e.KeyChar;
                switch (c)
                {
                    case '\n': EnterKey(); e.Handled = AcceptReturn; break;
                    case '\r': EnterKey(); e.Handled = AcceptReturn; break;
                    case '\x1b': EscapeKey(); break;
                    case '\t': TabKey(); break;
                    case '\b': Backspace(); break;
                    default: InsertChar(c); e.Handled = true; break;
                }
            }
            e.Handled = true;
        }

        protected virtual bool IsCharacterAllowed(char c)
        {
            if (string.IsNullOrEmpty(AllowedCharacters)) return true;
            return AllowedCharacters.IndexOf(c) >= 0;
        }


        /// <summary>
        /// Restarts the caret to blink.
        /// </summary>
        protected void RestartCaretBlink()
        {
            IsCaretVisible = true;
            caretTimer.Restart();
        }

        protected override void CheckInputControl()
        {
            if (Enabled && !ReadOnly)
            {
                Window.IsKeyboardVisible = true;
                if (isLongPressed) ShowClipboard();
            }
            else Window.IsKeyboardVisible = false;
        }

        /// <summary>
        /// Updates the location of the tooltip.
        /// </summary>
        protected void UpdateToolTip()
        {
            ToolTip tip = SelectionLength > 0 ? (ToolTip)CutCopyPasteToolTip : (ToolTip)SelectToolTip;
            if (tip.AnchorControl == this)
            {
                tip.AnchorBounds = GetToolTipAnchor();
            }
        }

        /// <summary>
        /// Shows either a clipboard tooltip or changes to a clipboard toolbar.
        /// </summary>
        public void ShowClipboard()
        {
            if (!IsFocused) return;
            ShowToolTip();
        }

        private void ShowToolTip()
        {
            if (SelectionLength > 0)
            {
                CutCopyPasteToolTip tip = CutCopyPasteToolTip;
                tip.AnchorBounds = GetToolTipAnchor();
                tip.AnchorControl = this;
                SelectToolTip.Hide();
                if (!tip.IsShown) tip.Show();
            }
            else
            {
                TextSelectToolTip tip = SelectToolTip;
                tip.AnchorBounds = GetToolTipAnchor();
                tip.AnchorControl = this;
                CutCopyPasteToolTip.Hide();
                if (!tip.IsShown) tip.Show();
            }
        }

        public override Rectangle GetToolTipAnchor()
        {
            Rectangle r;
            if (SelectionLength == 0)
            {
                r = Clipped(CaretRect);
            }
            else
            {
                Rectangle start = SelectionStartRect;
                Rectangle end = SelectionEndRect;
                r = new Rectangle(Math.Max((int)(start.X + end.X) >> 1, start.X), start.Y, 1, end.Bottom - start.Y);
                r = Clipped(r);
            }
            return r;
        }

        private Rectangle Clipped(Rectangle rect)
        {
            int top = rect.Top;
            int left = rect.Left;
            int right = rect.Right;
            int bottom = rect.Bottom;

            Rectangle clip = ClientVisibleBounds;
            if (top < clip.Top) top = clip.Top;
            else if (top > clip.Bottom) top = clip.Bottom;
            if (left < clip.Left) left = clip.Left;
            else if (left > clip.Right) left = clip.Right;
            if (right > clip.Right) right = clip.Right;
            else if (right < clip.Left) right = clip.Left;
            if (bottom > clip.Bottom) bottom = clip.Bottom;
            else if (bottom < clip.Top) bottom = clip.Top;

            if (right < left) right = left;
            if (bottom < top) bottom = top;
            int h = EndGrabber.Height;
            return new Rectangle(left, top - h, right - left, bottom - top + h);
        }

        /// <summary>
        /// Hides the current tooltip.
        /// </summary>
        protected void HideClipboard()
        {
            if (!IsFocused) return;
            HideTooltipClipboard();
        }

        protected void HideTooltipClipboard()
        {
            if (IsFocused)
            {
                CutCopyPasteToolTip.Hide();
                SelectToolTip.Hide();
            }
        }

        /// <summary>
        /// Selects the word under or near the caret.
        /// </summary>
        public virtual void SelectWord()
        {
            if (Text.Length > 0)
            {
                int selectionStart = SelectionStart;
                if (selectionStart >= text.Length) selectionStart = text.Length - 1;
                while (selectionStart > 0)
                {
                    if (!char.IsWhiteSpace(text[selectionStart])) break;
                    selectionStart--;
                }
                int start = text.LastIndexOfAny(wordbreaks, selectionStart);
                if (start < 0) start = 0; else start++;
                int end = text.IndexOfAny(wordbreaks, selectionStart);
                if (end < 0) end = text.Length;
                if (end <= start)
                {
                    start = 0;
                    end = text.Length;
                }
                Select(start, end - start);
            }
        }


        protected internal override void OnRender(Silvermoon.Core.Renderer renderer)
        {
            base.OnRender(renderer);
            if (chrome != null) renderer.RenderShape(chrome, renderer.Alpha);
        }

        #endregion
        #region events

        public static SparseEvent TextChangedEvent = new SparseEvent();

        /// <summary>
        /// Occurs when the text has changed.
        /// </summary>
        public event EventHandler TextChanged
        {
            add { AddHandler(TextChangedEvent, value); }
            remove { RemoveHandler(TextChangedEvent, value); }
        }

        #endregion
    }
}
